-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
executor: introduce a new execution framework for aggregate functions #6852
Conversation
/run-all-tests |
|
@winoros updated |
/run-all-tests |
/run-all-tests |
1 similar comment
/run-all-tests |
/run-all-tests |
/run-all-tests |
/run-all-tests |
@lysu PTAL |
executor/aggregate.go
Outdated
func (e *StreamAggExec) appendResult2Chunk(chk *chunk.Chunk) { | ||
func (e *StreamAggExec) appendResult2Chunk(chk *chunk.Chunk) error { | ||
if e.newAggFuncs != nil { | ||
fmt.Printf("StreamAggExec.appendResult2Chunk: use new aggfunc\n") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the debug log.
executor/aggfuncs/aggfuncs.go
Outdated
type AggFunc interface { | ||
// AllocPartialResult allocates a specific data structure to store the | ||
// partial result, initializes it, and converts it to a bype slice to return | ||
// back. Aggregate operator implementations, no mater whether it's a hash or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ mater/ matter
"github.com/pingcap/tidb/expression/aggregation" | ||
) | ||
|
||
// Build is used to build a specific AggFunc implementation according to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add .
at the end of this comment.
so as the other comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
executor/builder.go
Outdated
newAggFuncs = append(newAggFuncs, newAggFunc) | ||
} | ||
} | ||
if len(newAggFuncs) == len(v.AggFuncs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment for this check.
executor/builder.go
Outdated
newAggFuncs = append(newAggFuncs, newAggFunc) | ||
} | ||
} | ||
if len(newAggFuncs) == len(v.AggFuncs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment for this check.
executor/aggfuncs/aggfuncs.go
Outdated
|
||
type baseAggFunc struct { | ||
input []expression.Expression | ||
output []int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comments for these two args.
executor/aggregate.go
Outdated
func (e *StreamAggExec) fetchChildIfNecessary(ctx context.Context, chk *chunk.Chunk) error { | ||
if e.inputRow != e.inputIter.End() { | ||
return nil | ||
} | ||
|
||
if e.newAggFuncs != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why we need to consumeGroupRows here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before calling fetchChildIfNecessary
, we may have some unconsumed rows stored in e.childrenResults[0]
, we should consume them before calling e.children[0].Next
, which will reset e.childrenResults[0]
before execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
put this check between line 279 and line 280 may be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, if we put this check to that position, we have to call consumeGroupRows()
for every input row.
executor/aggfuncs/aggfuncs.go
Outdated
// input byte slice to the specific data structure which stores the partial | ||
// result and then calculates the final result and append that final result | ||
// to the chunk provided. | ||
AppendFinalResult2Chunk(sctx sessionctx.Context, partialBytes []byte, chk *chunk.Chunk) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ AppendFinalResult2Chunk/ GetFinalResult
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the original name, which indicates the result is appended to the output chunk
executor/aggfuncs/aggfuncs.go
Outdated
// back. Aggregate operator implementations, no mater whether it's a hash or | ||
// stream implementation, should hold this byte slice for further operations | ||
// like: "ResetPartialResult", "UpdatePartialResult". | ||
AllocPartialResult() []byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that we need another struct which contains partialResultBytes to handle the hashagg evaluation and aggfunc with distinct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for now, we can just add a map
field in a specific aggregate function implementation, during the execution of UpdatePartialResult
we use that map to deduplicate the input, when ResetPartialResult
, we reset that map.
executor/aggfuncs/func_avg.go
Outdated
@@ -100,6 +100,7 @@ func (e *avgDedup4Decimal) UpdatePartialResult(sctx sessionctx.Context, rowsInGr | |||
|
|||
type avgOriginal4Decimal struct { | |||
baseAvgDecimal | |||
deDuper map[types.MyDecimal]bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deDuper should be initialized
executor/aggfuncs/builder.go
Outdated
@@ -80,11 +81,20 @@ func buildAvg(aggFuncDesc *aggregation.AggFuncDesc, output []int) AggFunc { | |||
case aggregation.CompleteMode, aggregation.Partial1Mode: | |||
switch aggFuncDesc.Args[0].GetType().Tp { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should consider all the input types,
use EvalType here may be better?
executor/builder.go
Outdated
e.AggFuncs = append(e.AggFuncs, aggDesc.GetAggFunc()) | ||
newAggFunc := aggfuncs.Build(aggDesc, []int{i}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to pass a slice
since there is only one element in the slice?
// input PartialResult to the specific data structure which stores the | ||
// partial result and then calculates the final result and append that | ||
// final result to the chunk provided. | ||
AppendFinalResult2Chunk(sctx sessionctx.Context, pr PartialResult, chk *chunk.Chunk) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ AppendFinalResult2Chunk/ GetFinalResult
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the original name, which indicates the result is appended to the output chunk
executor/aggfuncs/aggfuncs.go
Outdated
type baseAggFunc struct { | ||
// input stores the input arguments for an aggregate function, we should | ||
// call input.EvalXXX to get the actual input data for this function. | ||
input []expression.Expression |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- s/ input/ args may be clearer.
- we do not need to define output as a slice,
since we only use it to append the final result to a chunk.
Do we need a |
@XuHuaiyu If we only decide to use it in the final or complete mode, we don't need to add the |
PTAL @coocood |
/run-all-tests tidb-test=pr/559 |
|
||
// for the new execution framework of aggregate functions | ||
newAggFuncs []aggfuncs.AggFunc | ||
partialResults []aggfuncs.PartialResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to hold partialResults
here instead of in each AggFunc
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'e better to let aggregate function implementations to be stateless. If not so, we have to allocate an aggregate function for every group, this is worse when we use it in the hash aggregate operator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, ClickHouse also has the same aggregate function framework: https://github.com/yandex/ClickHouse/blob/master/dbms/src/AggregateFunctions/AggregateFunctionAvg.h, and so does the Impala: https://github.com/cloudera/Impala/blob/cdh5-trunk/be/src/udf/udf.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it.
LGTM |
LGTM |
What have you changed? (mandatory)
Introduce a new interface named
AggFunc
defined in executor/aggfuncs/aggfuncs.go to refactor the execution framework of aggregate functions. The main usage of the new execution framework is:AllocPartialResult()
to allocate the struct to store the partial result for every aggregate functionUpdatePartialResult()
to update the partial result for every aggregate function, no mater whether the input is the original or partial data. The inputpartialBytes
will be converted to the specific partial result struct before update.ResetPartialResult()
to reset or reinitialize the partial result for every aggregate function. The inputpartialBytes
will be converted to the specific partial result struct before reinitialization.AppendFinalResult2Chunk()
to finalize the partial result to the inputchk
. The inputpartialBytes
will be converted to the specific partial result before finalization every group.The main improvements are:
UpdatePartialResult()
with[]chunk.Row
, we can reduce the total function calls, which saves a lot of time. And for stream aggregate, the input data for a aggregate function are stored sequentially in the input[]chunk.Row
, which can further improve the CPU cache performance.AllocPartialResult()
to allocate the specific struct to store the partial result for every aggregate function, we can reduce the redundant memory usage in the old structAggEvaluateContext
.Use
aggfuncs.Build
to create aAggFunc
according to theAggFuncDesc
. For now:AVG
StreamAggExec
if possiableWhat are the type of the changes (mandatory)?
How has this PR been tested (mandatory)?
Does this PR affect documentation (docs/docs-cn) update? (optional)
No
Benchmark result if necessary (optional)
test sql:
Before this PR:
After this PR:
The performance gain is about 96%